from __future__ import with_statement
import libtcodpy as T

from settings import *
from map import *
from mob import *
from item import *
from util import in_map
import ui

KEYS = [
    (['y', '7'], ('walk', (-1, -1))),
    (['k', '8', T.KEY_UP], ('walk', (0, -1))),
    (['u', '9'], ('walk', (1, -1))),
    (['h', '4', T.KEY_LEFT], ('walk', (-1, 0))),
    (['.', '5'], 'wait'),
    (['l', '6', T.KEY_RIGHT], ('walk', (1, 0))),
    (['b', '1'], ('walk', (-1, 1))),
    (['j', '2', T.KEY_DOWN], ('walk', (0, 1))),
    (['n', '3'], ('walk', (1, 1))),
    (['q', T.KEY_ESCAPE], 'quit'),
    (['g', ','], 'pick_up'),
    (['i'], 'inventory'),
    (['d'], 'drop'),
    (['>'], 'descend'),
    (['<'], 'ascend'),
    (['x', ';'], 'look'),
    (['?'], 'help'),
    ([T.KEY_F10], 'cycle_font'),
    (['W'], 'wizard'),
    # (['Z'], 'flip_horiz'),
    # (['X'], 'flip_vert'),
    # (['C'], 'invert')
]

def decode_key(key):
    for keys, cmd in KEYS:
        if key in keys:
            return cmd
    return None

class Quit(Exception):
    pass

class Game(object):
    def __init__(self, wizard):
        self.wizard = wizard

    def play(self):
        ui.init(self)
        ui.title_screen()
        self.start()
        self.loop()
        ui.close()

    def start(self):
        self.player = Player(self.wizard)
        self.turns = 0
        self.map_stack = [None for i in range(MAX_DLEVEL)]
        ui.message('Welcome to featurecreeper!')
        ui.message('Press ? for help.')
        self.start_map(1)

    def start_map(self, level):
        # should we emit some message on entry?
        # we cannot shelve the old map on the first turn
        if self.turns is not 0:
            self.map.last_turn = self.turns
            self.map_stack[self.map.level - 1] = self.map
        if not self.map_stack[level - 1]:
            self.map = Map(level)
        else:
            self.map = self.map_stack[level - 1]
            # forget stuff based on how long we've been gone
            time_gone = self.turns - self.map.last_turn
            for i in range(time_gone):
                x, y = randrange(MAP_W), randrange(MAP_H)
                self.map.tiles[x][y].forget_glyph()
            # and probably distort the remembered map
            choice = roll(1, 4)
            if choice == 4:
                self.map.invert_playfield()
            elif choice == 3:
                self.map.flip_playfield_horiz()
            elif choice == 2:
                self.map.flip_playfield_vert()

        x, y, _ = self.map.random_empty_tile()
        self.player.put(self.map, x, y)

    def loop(self):
        ui.draw_all()
        try:
            while True:
                if self.player.death:
                    if self.wizard:
                        if ui.prompt('Die? [yn]', 'yn') == 'n':
                            ui.new_ui_turn()
                            self.player.resurrect()
                            ui.draw_all()
                            continue
                    ui.prompt(
                        '[Game over: %s. Press ENTER]' % self.player.death,
                        [T.KEY_ENTER, T.KEY_KPENTER])
                    self.save_character_dump()
                    raise Quit()
                if self.player.won:
                    self.congratulate_player()
                    self.save_character_dump()
                    raise Quit()
                while self.player.action_turns > 0:
                    key = ui.readkey()
                    self.do_command(key)
                self.map.do_turn(self.turns)
                self.turns += 1
                ui.draw_all()
        except Quit:
            pass

    def do_command(self, key):
        cmd = decode_key(key)
        if cmd is None:
            return
        ui.new_ui_turn()
        if isinstance(cmd, str):
            getattr(self, 'cmd_'+cmd)()
        else:
            name, args = cmd
            getattr(self, 'cmd_'+name)(*args)
        ui.draw_all()

    def cmd_walk(self, dx, dy):
        self.player.walk(dx, dy)

    def cmd_wait(self):
        self.player.wait()

    def cmd_pick_up(self):
        tile = self.player.tile
        if tile.items == []:
            if tile.corpses:
                ui.message('You cannot bring yourself to pick up a corpse.')
            else:
                ui.message('There is nothing here to pick up.')
        elif len(tile.items) == 1:
            self.player.pick_up(tile.items[0])
        else:
            while True and tile.items:
                item = ui.select_item('Select an item to pick up',
                                      tile.items)
                if item:
                    self.player.pick_up(item)
                    ui.draw_all()
                else:
                    break

    def cmd_drop(self):
        item = ui.select_item('Select an item to drop', self.player.items)
        if item:
            self.player.drop(item)

    def cmd_inventory(self):
        item = ui.select_item('Select an item to use', self.player.items)
        if item:
            self.player.use(item)

    def cmd_ascend(self):
        if not isinstance(self.player.tile, StairUpTile):
            ui.message('Stand on an up stairway to ascend.')
            return
        ui.message('You climb upwards...')
        self.player.remove()
        self.turns += 1
        self.start_map(self.map.level-1)

    def cmd_descend(self):
        if not isinstance(self.player.tile, StairDownTile):
            ui.message('Stand on a down stairway to descend.')
            return
        ui.message('You climb downwards...')
        self.player.remove()
        self.turns += 1
        self.start_map(self.map.level+1)

    def cmd_quit(self):
        if ui.prompt('Quit? [yn]', 'yn') == 'y':
            raise Quit()
        else:
            ui.new_ui_turn()

    def cmd_cycle_font(self):
        ui.cycle_font()

    def cmd_help(self):
        ui.help_screen()

    def cmd_wizard(self):
        if self.wizard and self.map.level < MAX_DLEVEL:
            self.player.remove()
            self.start_map(self.map.level+1)

    def cmd_look(self):
        ui.look_mode()

    def cmd_flip_vert(self):
        self.map.flip_playfield_vert()

    def cmd_flip_horiz(self):
        self.map.flip_playfield_horiz()

    def cmd_invert(self):
        self.map.invert_playfield()

    def congratulate_player(self):
        effects = self.player.effects
        if 'r' in effects:
            ui.prompt(
                'You are the Dungeon Master now. Press ENTER',
                [T.KEY_ENTER, T.KEY_KPENTER])
        elif 'f' in effects:
            ui.prompt(
                'It will all be okay now. Press ENTER',
                [T.KEY_ENTER, T.KEY_KPENTER])
        elif 'd' in effects:
            ui.prompt(
                'The darkness finally recedes. Press ENTER',
                [T.KEY_ENTER, T.KEY_KPENTER])
        elif 'h' in effects:
            ui.prompt(
                'You have had such fun, you decide to stay. \
                    Press ENTER',
                [T.KEY_ENTER, T.KEY_KPENTER])
        else:
            ui.prompt(
                'Congratulations! You have won. Press ENTER',
                [T.KEY_ENTER, T.KEY_KPENTER])

    def save_character_dump(self):
        from datetime import datetime

        try:
            with open('character.txt', 'w') as f:
                f.write('%s - character dump\n\n' % TITLE)
                f.write(datetime.strftime(datetime.now(), '%d/%m/%Y %H:%M')+'\n\n')
                f.write('  MAP\n\n')
                for y in range(MAP_H):
                    for x in range(MAP_W):
                        tile = self.map.tiles[x][y]
                        if tile.mob and not isinstance(tile.mob, UnrealMonster):
                            c, _ = tile.mob.glyph
                        elif tile.items:
                            c, _ = tile.items[-1].glyph
                        elif all(not tile.transparent
                                 for tile in self.map.neighbor_tiles(x, y)):
                            c = ' '
                        else:
                            c, _ = tile.glyph
                        f.write(c)
                    f.write('\n')
                f.write('\n  LAST MESSAGES\n\n')
                for _, s, _ in ui.MESSAGES[-10:]:
                    f.write(s+'\n')
                f.write('\n  STATUS\n\n')
                f.write('\n'.join(ui.status_lines()))
                f.write('\n\n  INVENTORY\n\n')
                for item in self.player.items:
                    f.write('%s%s\n' %
                            ('*' if self.player.has_equipped(item) else ' ',
                             item.descr))
                if self.player.effects:
                    f.write('\n  INSANITY\n\n')
                    for eff in self.player.effects.values():
                        f.write(eff.long_descr + '\n')
        except IOError:
            pass
